﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using System.Xml;
using System.Xml.Linq;
using MyVocabulary.Popup;

namespace MyVocabulary
{
    public partial class Main : Form
    {
        #region Consts

        /// <summary>
        /// Tooltip messages used for the list view buttons
        /// </summary>
        protected static class BottomMenuTooltips
        {
            public const string AddFolder = "Add a new folder";
            public const string AddFile = "Add a new file";
            public const string RenameFolder = "Rename the selected folder";
            public const string DeleteFolder = "Delete the selected folder and all its contents";

            public const string TestFile = "Test your vocabulary or add new words in the selected file";
            public const string RenameFile = "Rename the selected file";
            public const string DeleteFile = "Delete the selected file";
        }

        /// <summary>
        /// Window messages
        /// </summary>
        protected static class FormMessages
        {
            public const string CurrentRootDirectory = "Current root directory";

            public const string RemoveFolderTitle = "Remove folder";
            public const string RemoveFolderMsg = "Are you sure you want to remove '{0}' and all its contents?";
            public const string RemoveFolderErrorMsg = "Error deleting folder";

            public const string RemoveFileTitle = "Remove file";
            public const string RemoveFileMsg = "Are you sure you want to remove the file '{0}'?";
            public const string RemoveFileErrorMsg = "Error deleting file";
        }

        /// <summary>
        /// The index (in the image list) used to display file and folder images in the list view
        /// </summary>
        protected static class FileSystemImageIndexes
        {
            public const int OpenedFolder = 0;
            public const int ClosedFolder = 1;
            public const int File = 2; //Files with the .xml extention
            public const int VerbFile = 3; //Files with the .vml extention
        }

        #endregion

        protected const string applicationDataFileName = "MyVocabularySetup.xml";

        /// <summary>
        /// The root directory for the list view
        /// </summary>
        protected string rootDirectory;

        /// <summary>
        /// When a folder in the list is collapsed it's subelements are removed from the list and kept in this dictonary
        /// </summary>
        protected Dictionary<ListViewItem, List<ListViewItem>> subItems = new Dictionary<ListViewItem, List<ListViewItem>>();

        /// <summary>
        /// Constructor
        /// </summary>
        public Main()
        {
            InitializeComponent();
            
            rootDirectory = GetRootDirectory();
            LoadLanguages();
        }

        #region Root Directory

        protected static string GetRootDirectory()
        {
            var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), applicationDataFileName);
            var defaultRootDirectory = GetDefaultRootDirectory();

            if (File.Exists(path))
            {
                //Read the default root directory from the application folder if an error occurs use the default root directory
                try
                {
                    var doc = XDocument.Load(path);
                    var element = doc.Root;
                    if (element == null || !element.HasElements)
                        return defaultRootDirectory;

                    foreach (var e in element.Elements())
                    {
                        if (e.Name != "KeyValuePair" || e.Attribute("key") == null ||
                            string.Compare(e.Attribute("key").Value, "rootDirectory", true) != 0 ||
                            e.Attribute("value") == null) continue;

                        var value = e.Attribute("value").Value;
                        if (!string.IsNullOrEmpty(value) && Directory.Exists(value))
                            return value;
                    }
                }
                catch
                {
                    //Hide the exception
                }
            }

            return defaultRootDirectory;           
        }

        /// <summary>
        /// Get a default value for the application root directory
        /// </summary>
        /// <returns></returns>
        protected static string GetDefaultRootDirectory()
        {
            var root = Application.StartupPath;
            var rootDir = (new DirectoryInfo(root)).GetDirectories("*root*");
            if (rootDir.Length > 0)
                root = rootDir[0].FullName;

            return root;
        }

        private void lnkViewRootDirectory_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {            
            new CustomMessageBox(rootDirectory, FormMessages.CurrentRootDirectory, CustomMessageBoxButtons.Cancel).ShowDialog();            
        }

        private void lnkChangeRootDirectory_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {
            //Open a folder dialog window
            folderBrowserDialog1.SelectedPath = rootDirectory;
            if (folderBrowserDialog1.ShowDialog(this) != DialogResult.OK)
                return;

            rootDirectory = folderBrowserDialog1.SelectedPath;
           
            //Save the new selected root folder path in application data    
            var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), applicationDataFileName);
            if(!File.Exists(path))
            {
                //Create a new file
                using(var xmlWriter = XmlWriter.Create(path))
                {                                  
                    xmlWriter.WriteStartDocument();
                    xmlWriter.WriteStartElement("configuration");

                    xmlWriter.WriteStartElement("KeyValuePair");
                    xmlWriter.WriteStartAttribute("key");
                    xmlWriter.WriteString("rootDirectory");
                    xmlWriter.WriteEndAttribute();
                    xmlWriter.WriteStartAttribute("value");
                    xmlWriter.WriteString(rootDirectory);
                    xmlWriter.WriteEndAttribute();
                    xmlWriter.WriteEndElement();

                    xmlWriter.WriteEndElement();
                    xmlWriter.WriteEndDocument();
                    xmlWriter.Flush();
                    xmlWriter.Close();                    
                }

                //Reload the languages list view
                LoadLanguages();

                return;
            }

            //Modify the existing file
            var doc = XDocument.Load(path);
            var element = doc.Root;
            if (element == null)
            {
                element = new XElement("configuration");
                doc.AddFirst(element);
            }

            var addNew = true;
            foreach (var elem in element.Elements())
            {
                if (elem.Name != "KeyValuePair" || elem.Attribute("key") == null ||
                    string.Compare(elem.Attribute("key").Value, "rootDirectory", true) != 0 ||
                    elem.Attribute("value") == null) continue;
               
                elem.SetAttributeValue("value", rootDirectory);
                addNew = false;
            }
            if (addNew)
            {
                var elem = new XElement("KeyValuePair");
                elem.SetAttributeValue("key", "rootDirectory");
                elem.SetAttributeValue("value", rootDirectory);
                element.Add(elem);
            }

            //Save the modifications
            doc.Save(path);

            //Reload the languages list view
            LoadLanguages();
        }

        #endregion
        
        #region lvLanguages

        #region Load List View

        protected void LoadLanguages()
        {
            lvLanguages.Items.Clear();
            lvLanguages.Columns.Clear();
            lvLanguages.Columns.Add("Name", 550);

            var dir = new DirectoryInfo(rootDirectory);

            foreach (var d in dir.GetDirectories())
            {
                AddSubFolders(d, 0);
            }

            foreach (var f in dir.GetFiles())
            {
                var isVocab = (string.Compare(f.Extension, ".xml", true) == 0);
                var isVerb = (string.Compare(f.Extension, ".vml", true) == 0);
                if (!isVocab && !isVerb)
                    continue;                
                var item = lvLanguages.Items.Add(new ListViewItem(
                    isVocab ? new Regex("\\.xml$").Replace(f.Name, string.Empty) : new Regex("\\.vml$").Replace(f.Name, string.Empty),
                    isVocab ? FileSystemImageIndexes.File : FileSystemImageIndexes.VerbFile));
                item.Name = f.FullName;
            }

            SetMenuButtons();
        }

        protected void AddSubFolders(DirectoryInfo dir, int index)
        {
            var item = lvLanguages.Items.Add(new ListViewItem(dir.Name, FileSystemImageIndexes.OpenedFolder));
            item.Name = dir.FullName;
            item.IndentCount = index;

            foreach (var sd in dir.GetDirectories())
            {
                AddSubFolders(sd, index + 1);
            }

            foreach (var f in dir.GetFiles())
            {
                var isVocab = (string.Compare(f.Extension, ".xml", true) == 0);
                var isVerb = (string.Compare(f.Extension, ".vml", true) == 0);
                if (!isVocab && !isVerb)
                    continue;

                item = lvLanguages.Items.Add(new ListViewItem(
                    isVocab ? new Regex("\\.xml$").Replace(f.Name, string.Empty) : new Regex("\\.vml$").Replace(f.Name, string.Empty),
                    isVocab ? FileSystemImageIndexes.File : FileSystemImageIndexes.VerbFile));
                item.Name = f.FullName;
                item.IndentCount = index + 1;
            }
        }

        #endregion

        #region lvLanguages_Click

        private void lvLanguages_MouseClick(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
                lvLanguages_ExpandCollapse();
            else
                lvLanguages_SetContextMenu();
        }

        private void lvLanguages_ExpandCollapse()
        {
            if (lvLanguages.SelectedItems.Count != 1)
                return;

            var item = lvLanguages.SelectedItems[0];
            var index = item.Index;
            switch (item.ImageIndex)
            {
                case FileSystemImageIndexes.OpenedFolder:
                    //collapse files
                    index++;
                    subItems[item] = new List<ListViewItem>();
                    while (lvLanguages.Items.Count > index)
                    {
                        if (lvLanguages.Items[index].IndentCount > item.IndentCount)
                        {
                            subItems[item].Add(lvLanguages.Items[index]);
                            lvLanguages.Items.RemoveAt(index);
                        }
                        else
                            break;
                    }
                    item.ImageIndex = FileSystemImageIndexes.ClosedFolder;
                    break;
                case FileSystemImageIndexes.ClosedFolder:
                    {
                        //expand files
                        var list = subItems[item];
                        foreach (var viewItem in list)
                        {
                            lvLanguages.Items.Insert(++index, viewItem);                            
                        }
                        item.ImageIndex = FileSystemImageIndexes.OpenedFolder;
                    }
                    break;
                default:
                    break;
            }        
        }

        private void lvLanguages_SetContextMenu()
        {
            if (lvLanguages.SelectedItems.Count > 0)
            {
                lvLanguages.ContextMenuStrip = lvLanguages.SelectedItems[0].ImageIndex == FileSystemImageIndexes.File || 
                                               lvLanguages.SelectedItems[0].ImageIndex == FileSystemImageIndexes.VerbFile
                                                   ? fileContextMenu
                                                   : folderContextMenu;                
            }
            else
            {
                lvLanguages.ContextMenuStrip = folderContextMenu;                
            }
        }

        private void folderContextMenu_Opening(object sender, System.ComponentModel.CancelEventArgs e)
        {
            folderContextMenu.Items["renameFolder"].Visible =
                folderContextMenu.Items["deleteFolder"].Visible = (lvLanguages.SelectedItems.Count > 0);
        }

        private void fileContextMenu_Opened(object sender, EventArgs e)
        {
            var visible = lvLanguages.SelectedItems.Count > 0;
            foreach (ToolStripItem item in fileContextMenu.Items)
            {
                item.Visible = visible;
            }
            fileContextMenu.Items["addNewFolder"].Visible = !visible;
            fileContextMenu.Items["addNewFile"].Visible = !visible;
        }

        #endregion

        private void lvLanguages_DoubleClick(object sender, EventArgs e)
        {
            testAndEditFile_Click(sender, e);
        }

        private void lvLanguages_SelectedIndexChanged(object sender, EventArgs e)
        {
            SetMenuButtons();
        }

        #region Bottom menu buttons

        protected void SetMenuButtons()
        {
            if (lvLanguages.SelectedItems.Count == 0)
            {
                btnMenu_1.Visible = true;
                SetAddFileButton(btnMenu_1);

                btnMenu_2.Visible = true;
                SetAddFolderButton(btnMenu_2);

                btnMenu_3.Visible = false;

                btnMenu_4.Visible = false;

                return;
            }

            if (lvLanguages.SelectedItems[0].ImageIndex == FileSystemImageIndexes.File ||
                lvLanguages.SelectedItems[0].ImageIndex == FileSystemImageIndexes.VerbFile)
            {
                btnMenu_1.Visible = true;
                SetDeleteFileButton(btnMenu_1);

                btnMenu_2.Visible = true;
                SetRenameFileButton(btnMenu_2);

                btnMenu_3.Visible = true;
                SetFileTestEditButton(btnMenu_3);

                btnMenu_4.Visible = false;

                return;
            }

            btnMenu_1.Visible = true;
            SetDeleteFolderButton(btnMenu_1);

            btnMenu_2.Visible = true;
            SetAddFileButton(btnMenu_2);

            btnMenu_3.Visible = true;
            SetAddFolderButton(btnMenu_3);

            btnMenu_4.Visible = true;
            SetRenameFolderButton(btnMenu_4);
        }

        protected void SetAddFolderButton(Button btn)
        {           
            btn.Image = Properties.Resources.AddFolder;
            toolTip1.SetToolTip(btn, BottomMenuTooltips.AddFolder);
            SetOnClick(btn, addFolder_Click);
        }

        protected void SetAddFileButton(Button btn)
        {
            btn.Image = Properties.Resources.AddFile;
            toolTip1.SetToolTip(btn, BottomMenuTooltips.AddFile);
            SetOnClick(btn, addFile_Click);
        }

        protected void SetRenameFolderButton(Button btn)
        {
            btn.Image = Properties.Resources.Rename;
            toolTip1.SetToolTip(btn, BottomMenuTooltips.RenameFolder);
            SetOnClick(btn, renameFolder_Click);
        }

        protected void SetDeleteFolderButton(Button btn)
        {
            btn.Image = Properties.Resources.Delete;
            toolTip1.SetToolTip(btn, BottomMenuTooltips.DeleteFolder);
            SetOnClick(btn, deleteFolder_Click);
        }

        protected void SetFileTestEditButton(Button btn)
        {
            btn.Image = Properties.Resources.Edit;
            toolTip1.SetToolTip(btn, BottomMenuTooltips.TestFile);
            SetOnClick(btn, testAndEditFile_Click);
        }

        protected void SetRenameFileButton(Button btn)
        {
            btn.Image = Properties.Resources.Rename;
            toolTip1.SetToolTip(btn, BottomMenuTooltips.RenameFile);
            SetOnClick(btn, renameFile_Click);
        }

        protected void SetDeleteFileButton(Button btn)
        {
            btn.Image = Properties.Resources.Delete;
            toolTip1.SetToolTip(btn, BottomMenuTooltips.DeleteFile);
            SetOnClick(btn, deleteFile_Click);
        }

        protected void SetOnClick(Button btn, EventHandler method)
        {
            btn.Click -= addFolder_Click;
            btn.Click -= addFile_Click;
            btn.Click -= renameFolder_Click;
            btn.Click -= deleteFolder_Click;
            btn.Click -= testAndEditFile_Click;
            btn.Click -= renameFile_Click;
            btn.Click -= deleteFile_Click;

            btn.Click += method;
        }

        #endregion

        #endregion

        #region Top Commmands

        private void btnRefresh_Click(object sender, EventArgs e)
        {
            LoadLanguages();
        }

        #endregion

        #region Folder Context Menu Commands

        private void renameFolder_Click(object sender, EventArgs e)
        {
            if (lvLanguages.SelectedItems.Count == 0) 
                return;

            var form = new RenameFolder(lvLanguages.SelectedItems[0].Name);
            form.OnFolderRenamed += OnFolderRenamed;
            form.ShowDialog(this);
        }

        public void OnFolderRenamed(object sender, EventArgs e)
        {
            if (!(sender is RenameFolder))
                return;

            var dir = ((RenameFolder)sender).NewFolderName;
            lvLanguages.SelectedItems[0].Text = dir.Name;
            lvLanguages.SelectedItems[0].Name = dir.FullName;            
        }

        private void addFolder_Click(object sender, EventArgs e)
        {
            var path = rootDirectory;
            if (lvLanguages.SelectedItems.Count > 0)
                path = lvLanguages.SelectedItems[0].Name;

            var form = new AddNewFolder(path);
            form.OnNewFolderCreated += OnNewFolderCreated;
            form.ShowDialog(this);
        }

        public void OnNewFolderCreated(object sender, EventArgs e)
        {
            if(!(sender is AddNewFolder))
                return;

            var dir = ((AddNewFolder)sender).CreatedFolder;
            var index = -1;
            var indentCount = -1;
                        
            if (lvLanguages.SelectedItems.Count > 0)
            {
                //make sure that the current node is expanded
                if (lvLanguages.SelectedItems[0].ImageIndex == FileSystemImageIndexes.ClosedFolder)
                    lvLanguages_ExpandCollapse();

                index = lvLanguages.SelectedItems[0].Index;
                indentCount = lvLanguages.SelectedItems[0].IndentCount;
            }

            //add the newly created folder in the list view
            var newPosition = index + 1;
            for (int i = newPosition, len = lvLanguages.Items.Count; i < len; i++)
            {
                if (lvLanguages.Items[i].IndentCount == indentCount + 1 && 
                    lvLanguages.Items[i].ImageIndex != FileSystemImageIndexes.File && 
                    lvLanguages.Items[i].ImageIndex != FileSystemImageIndexes.VerbFile)
                {
                    if (string.Compare(lvLanguages.Items[i].Text, dir.Name, true) < 0)
                        newPosition = i + 1;
                    else
                        break;
                }
                else if (lvLanguages.Items[i].IndentCount > indentCount + 1)
                    newPosition = i + 1;
                else
                    break;                
            }

            var item = lvLanguages.Items.Insert(newPosition, new ListViewItem(dir.Name, FileSystemImageIndexes.OpenedFolder));
            item.Name = dir.FullName;
            item.IndentCount = indentCount + 1;
        }

        private void addFile_Click(object sender, EventArgs e)
        {
            var path = rootDirectory;
            if (lvLanguages.SelectedItems.Count > 0)
                path = lvLanguages.SelectedItems[0].Name;

            var form = new AddNewFile(path);
            form.OnNewFileCreated += OnNewFileCreated;
            form.ShowDialog(this);
        }

        public void OnNewFileCreated(object sender, EventArgs e)
        {
            if (!(sender is AddNewFile))
                return;

            var file = ((AddNewFile)sender).CreatedFile;
            var index = -1;
            var indentCount = -1;

            if (lvLanguages.SelectedItems.Count > 0)
            {
                //make sure that the current node is expanded
                if (lvLanguages.SelectedItems[0].ImageIndex == FileSystemImageIndexes.ClosedFolder)
                    lvLanguages_ExpandCollapse();

                index = lvLanguages.SelectedItems[0].Index;
                indentCount = lvLanguages.SelectedItems[0].IndentCount;
            }

            //add the newly created file in the list view
            var newPosition = index + 1;
            for (int i = newPosition, len = lvLanguages.Items.Count; i < len; i++)
            {
                if (lvLanguages.Items[i].IndentCount == indentCount + 1)
                {
                    if ((lvLanguages.Items[i].ImageIndex != FileSystemImageIndexes.File && lvLanguages.Items[i].ImageIndex != FileSystemImageIndexes.VerbFile) || 
                        string.Compare(lvLanguages.Items[i].Text, file.Name, true) < 0)
                        newPosition = i + 1;
                    else
                        break;
                }
                else if (lvLanguages.Items[i].IndentCount > indentCount + 1)
                    newPosition = i + 1;
                else
                    break;
            }

            var isVocab = (string.Compare(file.Extension, ".xml", true) == 0);
            var item = lvLanguages.Items.Insert(newPosition, new ListViewItem(
                isVocab ? new Regex("\\.xml$").Replace(file.Name, string.Empty) : new Regex("\\.vml$").Replace(file.Name, string.Empty)
                , isVocab ? FileSystemImageIndexes.File : FileSystemImageIndexes.VerbFile));
            item.Name = file.FullName;
            item.IndentCount = indentCount + 1;
        }

        private void deleteFolder_Click(object sender, EventArgs e)
        {
            if (lvLanguages.SelectedItems.Count > 0)

                if (
                    (new CustomMessageBox(string.Format(FormMessages.RemoveFolderMsg, lvLanguages.SelectedItems[0].Text),
                                          FormMessages.RemoveFolderTitle, CustomMessageBoxButtons.YesNo)).ShowDialog()

                    ==
                    DialogResult.Yes)
                {
                    try
                    {
                        Directory.Delete(lvLanguages.SelectedItems[0].Name, true);
                        var index = lvLanguages.SelectedItems[0].Index;
                        var indentCount = lvLanguages.SelectedItems[0].IndentCount;
                        lvLanguages.Items.RemoveAt(index);
                        while (index < lvLanguages.Items.Count && lvLanguages.Items[index].IndentCount > indentCount)
                        {
                            lvLanguages.Items.RemoveAt(index);
                        }
                    }
                    catch
                    {
                        (new CustomMessageBox(FormMessages.RemoveFolderErrorMsg, FormMessages.RemoveFolderTitle,
                                              CustomMessageBoxButtons.OK)).ShowDialog();
                    }
                }
        }

        #endregion

        #region File Context Menu Commands

        private void testAndEditFile_Click(object sender, EventArgs e)
        {
            if (lvLanguages.SelectedItems.Count > 0)                                              
            {
                if(lvLanguages.SelectedItems[0].ImageIndex == FileSystemImageIndexes.File)
                {
                    var form = new Vocabulary(lvLanguages.SelectedItems[0].Name)
                                   {
                                       FileName = lvLanguages.SelectedItems[0].Text
                                   };
                    if (!form.ErrorLoadingFile)
                        form.Show(this);
                }
                else if(lvLanguages.SelectedItems[0].ImageIndex == FileSystemImageIndexes.VerbFile)
                {
                    var form = new Verbs(lvLanguages.SelectedItems[0].Name)
                    {
                        FileName = lvLanguages.SelectedItems[0].Text
                    };
                    if (!form.ErrorLoadingFile)
                        form.Show(this);                    
                }
            }
        }

        private void renameFile_Click(object sender, EventArgs e)
        {
            if (lvLanguages.SelectedItems.Count == 0) 
                return;

            var form = new RenameFile(lvLanguages.SelectedItems[0].Name);
            form.OnFileRenamed += OnFileRenamed;
            form.ShowDialog(this);
        }

        public void OnFileRenamed(object sender, EventArgs e)
        {
            if (!(sender is RenameFile))
                return;

            var file = ((RenameFile)sender).NewFileName;
            lvLanguages.SelectedItems[0].Text = new Regex("(\\.xml$)|(\\.vml$)").Replace(file.Name, string.Empty);
            lvLanguages.SelectedItems[0].Name = file.FullName;
        }

        private void deleteFile_Click(object sender, EventArgs e)
        {
            if (lvLanguages.SelectedItems.Count > 0)

                if (
                    (new CustomMessageBox(string.Format(FormMessages.RemoveFileMsg, lvLanguages.SelectedItems[0].Text),
                                          FormMessages.RemoveFileTitle, CustomMessageBoxButtons.YesNo)).ShowDialog()

                    ==
                    DialogResult.Yes)
                {
                    try
                    {
                        File.Delete(lvLanguages.SelectedItems[0].Name);
                        lvLanguages.Items.RemoveAt(lvLanguages.SelectedItems[0].Index);
                    }
                    catch
                    {
                        (new CustomMessageBox(FormMessages.RemoveFileErrorMsg, FormMessages.RemoveFileTitle,
                                              CustomMessageBoxButtons.OK)).ShowDialog();
                    }
                }
        }

        private void addNewFolder_Click(object sender, EventArgs e)
        {
            addFolder_Click(sender, e);
        }

        private void addNewFile_Click(object sender, EventArgs e)
        {
            addFile_Click(sender, e);
        }

        #endregion
    }
}
